package il.technion.ewolf.beehive;

import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.name.Named;

import il.technion.ewolf.beehive.handlers.BeehiveReplicationMessageHandler;
import il.technion.ewolf.beehive.handlers.ContentMessageHandler;
import il.technion.ewolf.beehive.handlers.FindValueHandler;
import il.technion.ewolf.beehive.op.MyFindValueOperation;
import il.technion.ewolf.kbr.Key;
import il.technion.ewolf.kbr.KeyFactory;
import il.technion.ewolf.kbr.Node;
import il.technion.ewolf.kbr.openkad.BootstrapNodesSaver;
import il.technion.ewolf.kbr.openkad.IncomingContentHandler;
import il.technion.ewolf.kbr.openkad.KadNet;
import il.technion.ewolf.kbr.openkad.NodeStorage;
import il.technion.ewolf.kbr.openkad.handlers.FindNodeHandler;
import il.technion.ewolf.kbr.openkad.handlers.ForwardHandler;
import il.technion.ewolf.kbr.openkad.handlers.PingHandler;
import il.technion.ewolf.kbr.openkad.handlers.StoreHandler;
import il.technion.ewolf.kbr.openkad.msg.ContentMessage;
import il.technion.ewolf.kbr.openkad.msg.ContentRequest;
import il.technion.ewolf.kbr.openkad.msg.StoreRequest;
import il.technion.ewolf.kbr.openkad.net.Communicator;
import il.technion.ewolf.kbr.openkad.net.MessageDispatcher;
import il.technion.ewolf.kbr.openkad.net.filter.IdMessageFilter;
import il.technion.ewolf.kbr.openkad.net.filter.TypeMessageFilter;
import il.technion.ewolf.kbr.openkad.op.FindValueOperation;
import il.technion.ewolf.kbr.openkad.op.JoinOperation;


public class BeehiveNet extends KadNet implements DHTNet{

	private final Provider<MyFindValueOperation> myFindValueOperationProvider;
	private final Provider<FindValueHandler> findValueHandlerProvider;
	private final Provider<ContentMessageHandler> contentHandlerProvider;
	private final Provider<StoreRequest> storeRequestProvider;
	private final Provider<MessageDispatcher<Node>> nodeMsgDispatcherProvider;
	private final KeyFactory keyFactory;
	private final Provider<BeehiveReplicationMessageHandler> replicationRequestProvider;
	
	@Inject
	BeehiveNet(
			Provider<MessageDispatcher<Object>> msgDispatcherProvider,
			Provider<MessageDispatcher<Node>> nodeMsgDispatcherProvider,
			Provider<JoinOperation> joinOperationProvider,
			Provider<ContentRequest> contentRequestProvider,
			Provider<ContentMessage> contentMessageProvider,
			Provider<IncomingContentHandler<Object>> incomingContentHandlerProvider,
			@Named("openkad.op.findvalue") Provider<FindValueOperation> findValueOperationProvider,
			Provider<ContentMessageHandler> p_contentHandlerProvider,
			Provider<MyFindValueOperation> p_myFindValueOperationProvider,
			Provider<StoreRequest> p_storeRequestProvider,
			Provider<FindNodeHandler> findNodeHandlerProvider,
			Provider<FindValueHandler> p_findValueHandlerProvider,
			Provider<PingHandler> pingHandler,
			Provider<StoreHandler> storeHandlerProvider,
			Provider<ForwardHandler> forwardHandlerProvider,
			
			@Named("openkad.local.node") Node localNode,
			Communicator kadServer,
			NodeStorage nodeStorage,
			KeyFactory keyFactory,
			@Named("openkad.executors.client") ExecutorService clientExecutor,
			@Named("openkad.bucket.kbuckets.maxsize") int bucketSize,
			@Named("openkad.refresh.task") TimerTask refreshTask,
			BootstrapNodesSaver bootstrapNodesSaver,
			
			KeyFactory factory,
			//testing
			@Named("openkad.testing.findNodeHopsHistogram") List<Integer> findNodeHopsHistogram,
			Provider<BeehiveReplicationMessageHandler> replicationRequestProvider){
		super(msgDispatcherProvider, joinOperationProvider, contentRequestProvider,
				contentMessageProvider, incomingContentHandlerProvider,
				findValueOperationProvider, findNodeHandlerProvider, pingHandler,
				storeHandlerProvider, forwardHandlerProvider, localNode, kadServer,
				nodeStorage, keyFactory, clientExecutor, bucketSize, refreshTask,
				bootstrapNodesSaver, findNodeHopsHistogram);
		
		this.myFindValueOperationProvider = p_myFindValueOperationProvider;
		this.findValueHandlerProvider = p_findValueHandlerProvider;
		this.contentHandlerProvider = p_contentHandlerProvider;
		this.storeRequestProvider = p_storeRequestProvider;
		this.nodeMsgDispatcherProvider = nodeMsgDispatcherProvider;
		this.keyFactory = factory;
		this.replicationRequestProvider = replicationRequestProvider;
	}

	@Override
	public void create() throws IOException {
		// bind communicator and register all handlers
		findValueHandlerProvider.get().register();
		contentHandlerProvider.get().register();
		replicationRequestProvider.get().register();
		super.create();
		
	}

	@Override
	public Serializable getValue(Key key) {
		MyFindValueOperation op = myFindValueOperationProvider.get()
				.setKey(key);
			
		Serializable result = op.doFindValue();

//		if (result != null)
//			System.out.println("Number queried: " + op.getNrQueried() + ". Value: " + result.toString());
//		else 
//			System.out.println("Number queried: " + op.getNrQueried() + ". Value not found.");
		return result;
	}

	@Override
	public void setValue(Serializable value) {
		Key key = keyFactory.create(value.toString());
		
		List<Node> toStore = findNode(key);
		
		for (Node n : toStore){
			StoreRequest msg = storeRequestProvider.get()
					.setTag("SAVE")
					.setContent(value);
				
			nodeMsgDispatcherProvider.get()
					.addFilter(new IdMessageFilter(msg.getId()))
					.addFilter(new TypeMessageFilter(StoreRequest.class))
					.setConsumable(true)
					.send(n, msg);
		}
	}
}
